﻿using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Xml.Serialization;
using MathService.Expressions.Complex;
using DST = System.Diagnostics.DebuggerStepThroughAttribute;

namespace MathService
{
    /// <summary>Комплексное число</summary>
    [Serializable]
    [TypeConverter(typeof(ComplexConverter))]
    public partial struct Complex : IEquatable<Complex>, ICloneable<Complex>, IFormattable
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Точность вычисления тригонометрических функций 3e-16 </summary>
        /// <remarks>Определено приближённо</remarks>
        public const double Epsilon = 3.0e-16;

        /// <summary>Синус комплексного аргумента</summary>
        /// <param name="z">Комплексный аргумент</param>
        /// <returns>Значение синуса</returns>
        public static Complex Sin(Complex z)
        {
            var re = z.Re;
            var im = z.Im;
            var sin_a = Math.Sin(re);
            var cos_a = Math.Cos(re);
            var exp_b = Math.Exp(im);
            exp_b -= 1 / exp_b;

            return new Complex(.5 * sin_a * exp_b, .5 * cos_a * exp_b);
        }

        /// <summary>Косинус комплексного аргумента</summary>
        /// <param name="z">Комплексный аргумент</param>
        /// <returns>Значение косинуса</returns>
        public static Complex Cos(Complex z)
        {
            var re = z.Re;
            var im = z.Im;
            var sin_a = Math.Sin(re);
            var cos_a = Math.Cos(re);
            var exp_b = Math.Exp(im);
            var exp_b_inv = 1 / exp_b;

            return new Complex(.5 * cos_a * (exp_b - exp_b_inv), -.5 * sin_a * (exp_b + exp_b_inv));
        }

        /// <summary>Логарифм комплексного аргумента</summary>
        /// <param name="Im">Комплексный аргумент</param>
        /// <returns>Значение логарифма</returns>
        public static Complex Ln(double Im)
        {
            return new Complex(Math.Log(Im), Math.Abs(Im) < double.Epsilon ? 0 : (Im > 0 ? Consts.pi05 : -Consts.pi05));
        }

        ///<summary>НАтуральный логорифм комплексного числа</summary>
        ///<param name="z">Комплексное число</param>
        ///<returns>Натуральный логорифм</returns>
        public static Complex Ln(Complex z)
        {
            return new Complex(.5 * Math.Log(z.f_Re * z.f_Re + z.f_Im * z.f_Im), z.Arg);
        }

        ///<summary>Логорифм мномого числа по действительному основанию</summary>
        ///<param name="Im">Мнимое число</param>
        ///<param name="b">Действительное основание логорифма</param>
        ///<returns>Логорифм мнимого числа по действительному основанию</returns>
        public static Complex Log(double Im, double b)
        {
            return new Complex(
                Math.Log(Im, b),
                Math.Abs(Im) < double.Epsilon
                ? 0
                : (Im > 0
                    ? Consts.pi05
                    : -Consts.pi05) * Math.Log(Math.E, b));
        }

        /// <summary>Логарифм комплексного числа по действительному аргументу</summary>
        /// <param name="z">Комплексное число</param>
        /// <param name="b">Действительное основание логорифма</param>
        /// <returns>Логорифм комплексного числа по действительному основанию</returns>
        public static Complex Log(Complex z, double b)
        {
            return new Complex(.5 * Math.Log(z.f_Re * z.f_Re + z.f_Im * z.f_Im, b), z.Arg * Math.Log(Math.E, b));
        }

        /// <summary>Экспоненциальная форма числа Z = e^j*Arg</summary>
        /// <param name="Arg">Аргумент</param>
        /// <returns>Комплексное число в экспоненциальной форме записи</returns>
        public static Complex Exp(double Arg) { return new Complex(Math.Cos(Arg), Math.Sin(Arg)); }

        /// <summary>Экспоненциальная форма числа Z = Abs * e^j*Arg</summary>
        /// <param name="Abs">Модуль числа</param>
        /// <param name="Arg">Аргумент числа</param>
        /// <returns>Комплексное число в экспоненциальной форме</returns>
        public static Complex Exp(double Abs, double Arg)
        {
            return new Complex(Abs * Math.Cos(Arg), Abs * Math.Sin(Arg));
        }

        ///// <summary>Алгебраическая форма записи комплексного числа</summary>
        ///// <param name="Re">Действительная часть числа</param>
        ///// <returns>Комплексное число в алгебраической форме записи</returns>
        //public static Complex Mod(double Re) { return new Complex(Re); }

        /// <summary>Алгебраическая форма записи комплексного числа</summary>
        /// <param name="Re">Действительная часть числа</param>
        /// <param name="Im">Мнимиая часть числа</param>
        /// <returns>Комплексное число в алгебраической форме записи</returns>
        public static Complex Mod(double Re, double Im = 0) { return new Complex(Re, Im); }

        /// <summary>Алгебраическая форма записи комплексного числа</summary>
        /// <param name="Re">Действительная часть числа</param>
        /// <param name="Im">Мнимиая часть числа</param>
        /// <returns>Комплексное число в алгебраической форме записи</returns>
        public static Complex Mod(double Re, Complex Im) { return new Complex(Re + Im.Re, Im.Im); }

        /// <summary>Алгебраическая форма записи комплексного числа</summary>
        /// <param name="Re">Действительная часть числа</param>
        /// <param name="Im">Мнимиая часть числа</param>
        /// <returns>Комплексное число в алгебраической форме записи</returns>
        public static Complex Mod(Complex Re, Complex Im) { return new Complex(Re.Re + Im.Re, Re.Im + Im.Im); }

        /// <summary>Алгебраическая форма записи комплексного числа</summary>
        /// <param name="Re">Действительная часть числа</param>
        /// <param name="Im">Мнимиая часть числа</param>
        /// <returns>Комплексное число в алгебраической форме записи</returns>
        public static Complex Mod(Complex Re, double Im) { return new Complex(Re.Re, Re.Im + Im); }

        /// <summary>Действительное "комплексное" число</summary>
        public static Complex Real { get { return new Complex(1); } }

        /// <summary>Мнимая единица</summary>
        public static Complex i { get { return new Complex(0, 1); } }

        ///<summary>Создать массив комплексных чисел</summary>
        ///<param name="Re">Массив действительных чисел</param>
        ///<param name="Im">Массив мнимых чисел</param>
        ///<returns>Массив комплексных чисел</returns>
        public static Complex[] CreateArray(double[] Re, double[] Im)
        {
            Contract.Requires(Re.Length == Im.Length, "Размеры масивов не равны");
            return new Complex[Re.Length].Initialize(j => new Complex(Re[j], Im[j]));
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Действительная часть</summary>
        private double f_Re;

        /// <summary>Мнимая часть</summary>
        private double f_Im;

        /* -------------------------------------------------------------------------------------------- */

        public CopmlexConstantExpression Expression { get { return (CopmlexConstantExpression)ComplexExpression.Mod(f_Re, f_Im); } }

        /// <summary>Действительная часть</summary>
        public double Re { [DST] get { return f_Re; } [DST] set { f_Re = value; } }

        /// <summary>Мнимая часть</summary>
        public double Im { [DST] get { return f_Im; } [DST] set { f_Im = value; } }

        /// <summary>X * X^* = Re(X)^2 + Im(X)^2</summary>
        [XmlIgnore]
        public double Power { [DST] get { return f_Re * f_Re + f_Im * f_Im; } }

        /// <summary>Модуль</summary>
        [XmlIgnore]
        public double Abs { [DST]get { return Math.Sqrt(Power); } [DST] set { SetAbsArg(value, Arg); } }

        /// <summary>Аргумент</summary>
        [XmlIgnore]
        public double Arg
        {
            [DST]
            get
            {
                return f_Re.Equals(0)
                    ? (f_Im.Equals(0)      // Re == 0
                        ? 0                                 //  Im == 0 => 0
                        : Math.Sign(f_Im) * Consts.pi05)    //  Im != 0 => pi/2 * sign(Im)
                    : (f_Im.Equals(0)      // Re != 0
                        ? (Math.Sign(f_Re) > 0
                            ? 0
                            : Consts.pi)
                        : Math.Atan2(f_Im, f_Re));          //  Im != 0 => atan(Im/Re)
            }
            [DST]
            set { SetAbsArg(Abs, value); }
        }

        /// <summary>Комплексно сопряжённое число</summary>
        [XmlIgnore]
        public Complex ComplexConjugate { [Pure, DST] get { return new Complex(Re, -Im); } [DST] set { Re = value.Re; Im = -value.Im; } }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Комплексное число</summary>
        /// <param name="Re">Действительная часть</param>
        /// <param name="Im">Мнимая часть</param>
        [DST]
        public Complex(double Re, double Im = 0) { f_Re = Re; f_Im = Im; }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Установить действительную и мнимую часть</summary>
        /// <param name="re">Действительная часть</param>
        /// <param name="im">Мнимая часть</param>
        [DST]
        public void SetReIm(double re, double im = 0) { f_Re = re; f_Im = im; }

        /// <summary>Установить модуль и аргумент</summary>
        /// <param name="abs">Модуль</param>
        /// <param name="arg">Аргумент</param>
        [DST]
        public void SetAbsArg(double abs, double arg = 0) { f_Re = abs * Math.Cos(arg); f_Im = abs * Math.Sin(arg); }

        /* -------------------------------------------------------------------------------------------- */

        [DST]
        public string ToString_Exponent()
        {
            var abs = Abs;
            var arg = Arg;
            return abs.Equals(1)
                        ? (arg.Equals(0) ? "1" : string.Format("e{{i{0}}}", Arg))
                        : (arg.Equals(0) ? abs.ToString() : string.Format("{0}e{{i{1}}}", Abs, Arg));
        }
        [DST]
        public string ToString_Exponent_pi()
        {
            var abs = Abs;
            var arg = Arg / Consts.pi;
            return abs.Equals(1)
                        ? (arg.Equals(0) ? "1" : string.Format("e{{i{0}pi}}", Arg))
                        : (arg.Equals(0) ? abs.ToString() : string.Format("{0}e{{i{1}pi}}", Abs, Arg));
        }
        [DST]
        public string ToString_Exponent_Deg()
        {
            var abs = Abs;
            var arg = Arg.ToDeg();
            return abs.Equals(1)
                        ? (arg.Equals(0) ? "1" : string.Format("e{{i{0}deg}}", Arg))
                        : (arg.Equals(0) ? abs.ToString() : string.Format("{0}e{{i{1}deg}}", Abs, Arg));
        }

        /// <summary>Строковый эквивалент</summary>
        /// <returns>Строковый эквивалент</returns>
        [DST]
        public override string ToString()
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString();
            var lv_Imagen = string.Format("{0}i", Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString() : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        /// <summary>Преобразование в строковый формат</summary>
        /// <param name="Format">Формат преобразования</param>
        /// <returns>Строковое представление</returns>
        [DST]
        public string ToString(string Format)
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString(Format);
            var lv_Imagen = string.Format("{0}i",
                Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString(Format) : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        [DST]
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if(Math.Abs(Re) < double.Epsilon && Math.Abs(Im) < double.Epsilon) return "0";
            var lv_Real = Re.ToString(format, formatProvider);
            var lv_Imagen = string.Format("{0}i",
                Math.Abs(Math.Abs(Im) - 1) > double.Epsilon ? Math.Abs(Im).ToString(format, formatProvider) : "");
            if(Im < 0) lv_Imagen = "-" + lv_Imagen;
            return string.Format("{0}{1}",
                Math.Abs(Re) > double.Epsilon
                    ? string.Format("{0}{1}", lv_Real, Im > 0 ? "+" : "")
                    : "",
                Math.Abs(Im) > double.Epsilon ? lv_Imagen : "");
        }

        /// <summary>Хеш-код числа</summary>
        /// <returns>Хеш-код числа</returns>
        [DST]
        public override int GetHashCode() { unchecked { return (f_Re.GetHashCode() * 0x18d) ^ f_Im.GetHashCode(); } }

        /// <summary>Получение клона</summary>
        /// <returns>Клон числа</returns>
        [DST]
        public Complex Clone() { return new Complex(f_Re, f_Im); }

        /// <summary>Получение клона</summary>
        /// <returns>Клон числа</returns>
        [DST]
        object ICloneable.Clone() { return Clone(); }

        /// <summary>Сравнение с другим объектом</summary>
        /// <param name="obj">Сравниваемый объект</param>
        /// <returns>Истина, если объект - комплексное число и чдействительная и мнимая части равны</returns>
        [DST]
        public override bool Equals(object obj) { return (obj is Complex) && Equals((Complex)obj); }

        #region IEquatable<Complex> Members

        /// <summary>Проверка на идентичность</summary>
        /// <param name="other">Проверяемое значение</param>
        /// <returns>Истина, если числа идентичны</returns>
        [DST]
        public bool Equals(Complex other)
        {
            return Math.Abs(f_Re - other.f_Re) < double.Epsilon
                && Math.Abs(f_Im - other.f_Im) < double.Epsilon;
        }

        /// <summary>Проверка на идентичность</summary>
        /// <param name="other">Проверяемое число</param>
        /// <returns>Истина, если числа идентичны</returns>
        [DST]
        bool IEquatable<Complex>.Equals(Complex other) { return Equals(other); }

        #endregion

        /// <summary>Уменьшить значение</summary>
        /// <param name="Re">Значение, прибавляемое к действительной части</param>
        /// <param name="Im">Значение, прибавляемое к мнимой части</param>
        /// <returns></returns>
        [DST]
        public Complex Inc(double Re, double Im)
        {
            f_Re += Re;
            f_Im += Im;
            return this;
        }

        /// <summary>Увеличить значение</summary>
        /// <param name="z">Прибавляемое значение</param>
        /// <returns>Увеличенное значение</returns>
        [DST]
        public Complex Inc(Complex z) { return Inc(z.Re, z.Im); }

        /// <summary>Уменьшить значение</summary>
        /// <param name="Re">Значение, отнимаемое от действительной части</param>
        /// <param name="Im">Значение, отнимаемое от мнимой части</param>
        /// <returns>Уменьшенное значение комплексного числа</returns>
        [DST]
        public Complex Dec(double Re = 1, double Im = 1)
        {
            f_Re -= Re;
            f_Im -= Im;
            return this;
        }

        /// <summary>Уменьшить значение</summary>
        /// <param name="z">Вычитаемое комплексное число</param>
        /// <returns>Уменьшенное значение комплексного числа</returns>
        [DST]
        public Complex Dec(Complex z) { return Dec(z.Re, z.Im); }

        /// <summary>Поворот вектора комплексного числа на угол</summary>
        /// <param name="w">Угол поворота вектора в комплексной плоскости</param>
        /// <returns>Комплексное число, повёрнутое на угол</returns>
        [DST]
        public Complex Rotate(double w)
        {
            var sin = Math.Sin(w);
            var cos = Math.Cos(w);
            var re = f_Re;
            var im = f_Im;

            return new Complex(f_Re = re * cos - im * sin, f_Im = re * cos + im * sin);
        }

        /// <summary>Округление числа</summary>
        /// <param name="DigitsCount">Число разрядов</param>
        /// <returns>Округлённое число</returns>
        public Complex Round(int DigitsCount = 0)
        {
            return new Complex(Math.Round(f_Re, DigitsCount), Math.Round(f_Im, DigitsCount));
        }

        /// <summary>Вычисление квадратного корня числа</summary>
        /// <returns>Квадратный корень числа</returns>
        public Complex Sqrt() { return this ^ 0.5; }

    }
}
